// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef ZIRCON_SYSTEM_DEV_DISPLAY_INTEL_I915_REGISTERS_DPLL_H_
#define ZIRCON_SYSTEM_DEV_DISPLAY_INTEL_I915_REGISTERS_DPLL_H_

#include <assert.h>
#include <hwreg/bitfields.h>

#include "registers-ddi.h"

namespace registers {

static constexpr uint32_t kDpllCount = 4;

enum Dpll {
  DPLL_INVALID = -1,
  DPLL_0 = 0,
  DPLL_1,
  DPLL_2,
  DPLL_3,
};

static const Dpll kDplls[kDpllCount] = {
    DPLL_0,
    DPLL_1,
    DPLL_2,
    DPLL_3,
};

// DPLL_CTRL1
class DpllControl1 : public hwreg::RegisterBase<DpllControl1, uint32_t> {
 public:
  hwreg::BitfieldRef<uint32_t> dpll_hdmi_mode(Dpll dpll) {
    int bit = dpll * 6 + 5;
    return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit, bit);
  }

  hwreg::BitfieldRef<uint32_t> dpll_ssc_enable(Dpll dpll) {
    int bit = dpll * 6 + 4;
    return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit, bit);
  }

  hwreg::BitfieldRef<uint32_t> dpll_link_rate(Dpll dpll) {
    int bit = dpll * 6 + 1;
    return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit + 2, bit);
  }
  static constexpr int kLinkRate2700Mhz = 0;  // DisplayPort 5.4 GHz
  static constexpr int kLinkRate1350Mhz = 1;  // DisplayPort 2.7 GHz
  static constexpr int kLinkRate810Mhz = 2;   // DisplayPort 1.62 GHz
  static constexpr int kLinkRate1620Mhz = 3;  // DisplayPort 3.24 GHz
  static constexpr int kLinkRate1080Mhz = 4;  // DisplayPort 2.16 GHz
  static constexpr int kLinkRate2160Mhz = 5;  // DisplayPort 4.32 GHz

  hwreg::BitfieldRef<uint32_t> dpll_override(Dpll dpll) {
    int bit = dpll * 6;
    return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit, bit);
  }

  static auto Get() { return hwreg::RegisterAddr<DpllControl1>(0x6c058); }
};

// DPLL_CTRL2
class DpllControl2 : public hwreg::RegisterBase<DpllControl2, uint32_t> {
 public:
  hwreg::BitfieldRef<uint32_t> ddi_clock_off(Ddi ddi) {
    int bit = 15 + ddi;
    return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit, bit);
  }

  hwreg::BitfieldRef<uint32_t> ddi_clock_select(Ddi ddi) {
    int bit = ddi * 3 + 1;
    return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit + 1, bit);
  }

  hwreg::BitfieldRef<uint32_t> ddi_select_override(Ddi ddi) {
    int bit = ddi * 3;
    return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit, bit);
  }

  static auto Get() { return hwreg::RegisterAddr<DpllControl2>(0x6c05c); }
};

// DPLL_CFGCR1
class DpllConfig1 : public hwreg::RegisterBase<DpllConfig1, uint32_t> {
 public:
  DEF_BIT(31, frequency_enable);
  DEF_FIELD(23, 9, dco_fraction);
  DEF_FIELD(8, 0, dco_integer);

  static auto Get(Dpll dpll) {
    ZX_ASSERT(dpll == DPLL_1 || dpll == DPLL_2 || dpll == DPLL_3);
    return hwreg::RegisterAddr<DpllConfig1>(0x6c040 + ((dpll - 1) * 8));
  }
};

// DPLL_CFGCR2
class DpllConfig2 : public hwreg::RegisterBase<DpllConfig2, uint32_t> {
 public:
  DEF_FIELD(15, 8, qdiv_ratio);
  DEF_BIT(7, qdiv_mode);

  DEF_FIELD(6, 5, kdiv_ratio);
  static constexpr uint8_t kKdiv5 = 0;
  static constexpr uint8_t kKdiv2 = 1;
  static constexpr uint8_t kKdiv3 = 2;
  static constexpr uint8_t kKdiv1 = 3;

  DEF_FIELD(4, 2, pdiv_ratio);
  static constexpr uint8_t kPdiv1 = 0;
  static constexpr uint8_t kPdiv2 = 1;
  static constexpr uint8_t kPdiv3 = 2;
  static constexpr uint8_t kPdiv7 = 4;

  DEF_FIELD(1, 0, central_freq);
  static constexpr uint8_t k9600Mhz = 0;
  static constexpr uint8_t k9000Mhz = 1;
  static constexpr uint8_t k8400Mhz = 3;

  static auto Get(int dpll) {
    ZX_ASSERT(dpll == DPLL_1 || dpll == DPLL_2 || dpll == DPLL_3);
    return hwreg::RegisterAddr<DpllConfig2>(0x6c044 + ((dpll - 1) * 8));
  }
};

// Virtual register which unifies the dpll enable bits (which are spread
// across 4 registers)
class DpllEnable : public hwreg::RegisterBase<DpllEnable, uint32_t> {
 public:
  DEF_BIT(31, enable_dpll);

  static auto Get(Dpll dpll) {
    if (dpll == 0) {
      return hwreg::RegisterAddr<DpllEnable>(0x46010);  // LCPLL1_CTL
    } else if (dpll == 1) {
      return hwreg::RegisterAddr<DpllEnable>(0x46014);  // LCPLL2_CTL
    } else if (dpll == 2) {
      return hwreg::RegisterAddr<DpllEnable>(0x46040);  // WRPLL_CTL1
    } else {                                            // dpll == 3
      return hwreg::RegisterAddr<DpllEnable>(0x46060);  // WRPLL_CTL2
    }
  }
};

// DPLL_STATUS
class DpllStatus : public hwreg::RegisterBase<DpllStatus, uint32_t> {
 public:
  hwreg::BitfieldRef<uint32_t> dpll_lock(Dpll dpll) {
    int bit = dpll * 8;
    return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit, bit);
  }

  static auto Get() { return hwreg::RegisterAddr<DpllStatus>(0x6c060); }
};

// LCPLL1_CTL
class Lcpll1Control : public hwreg::RegisterBase<Lcpll1Control, uint32_t> {
 public:
  DEF_BIT(30, pll_lock);

  static auto Get() { return hwreg::RegisterAddr<Lcpll1Control>(0x46010); }
};

}  // namespace registers

#endif  // ZIRCON_SYSTEM_DEV_DISPLAY_INTEL_I915_REGISTERS_DPLL_H_
